﻿using System.Collections;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.SceneManagement;

//最晚执行
public class ConnectionManager : MonoBehaviour
{   
    public static ConnectionManager Instance;
    
    [SerializeField] GameObject multiPlayPrefab;
    //多人模式的房间密码
    public string AccessPassword;
    public int MaxPlayers { get; private set; }
    bool isShutDown = false;

    private void Awake()
    {        
        if (Instance == null)
            Instance = this;
        else
            Destroy(gameObject);        
    }

    private void Start()
    {
        var nm = NetworkManager.Singleton;
        //启用验证
        nm.NetworkConfig.ConnectionApproval = true;
    
        nm.OnConnectionEvent += OnConnection;
    }

    private void OnDestroy()
    {        
        var nm= NetworkManager.Singleton;
        if (nm != null)
            nm.OnConnectionEvent -= OnConnection;
    }
    
    //manager是本地的NetworkManager.Singleton
    void OnConnection(NetworkManager manager, ConnectionEventData eventData)
    {
        if (!manager.IsListening)
        {
            Debug.Log("Not listing so don't do anything");
            return;
        }
        //ClientDisconnected只有主机和本地会调用
        if (eventData.EventType == ConnectionEvent.ClientDisconnected)
        {            
            if(manager.IsHost)
            {
                if(eventData.ClientId != manager.LocalClientId)
                {
                    MultiPlayManager.Instance.RemovePlayer(eventData.ClientId);                    
                }
                else if (!isShutDown)
                {
                    Debug.Log($"{manager.LocalClientId} exit connected by not known reason");
                    ShutDown();
                }
            }
            else if (!isShutDown)
            {
                Debug.Log($"{manager.LocalClientId} exit connected by {manager.DisconnectReason}");
                ShutDown();                
            }
        }
    }  

    public void ShutDown()
    {
        isShutDown = true;        
        if(MultiPlayManager.Instance!=null)
            Destroy(MultiPlayManager.Instance.gameObject);
        StopAllCoroutines();
        NetworkManager.Singleton.Shutdown();
        MySceneManager.Instance.LoadSceneAsync("Menu");
    }

    public void StartHost(string pwd,string name,int maxPlayers=4)
    {        
        var nm = NetworkManager.Singleton;

        isShutDown = false;
        //设置房间密码
        AccessPassword = pwd;
        MaxPlayers = maxPlayers;
        //设置验证回调
        nm.ConnectionApprovalCallback = ApprovalCheck;
        var transport = nm.NetworkConfig.NetworkTransport as UnityTransport;
        ushort[] ports = new ushort[10];
        for(ushort i=0;i<10;i++)
            ports[i]=(ushort)(i+7777);
        string address= GetLocalIPV4();        
        foreach (var port in ports)
        {
            if (IsPortAvailable(port))
            {
                transport.SetConnectionData(address, port, "0.0.0.0");
                if(nm.StartHost())
                    Debug.Log("Host started at " + address + ":" + port);
                break;
            }            
            else
            {                
                Debug.LogWarning("Error to start host with port " + port);
            }
        }

        var netObj = Instantiate(multiPlayPrefab);
        MultiPlayManager.Instance.NetworkObject.Spawn();
        MultiPlayManager.Instance.Players.Add(new() { id = nm.LocalClientId, name = name,isLive=true});

        //活动场景同步
        nm.SceneManager.ActiveSceneSynchronizationEnabled = true;
        //主机通过设置此回调可以取消让客户端同步的场景
        nm.SceneManager.VerifySceneBeforeLoading += (sceneIndex, sceneName, mode) =>
        {
            if (sceneName == "Persistent") return false;
            return true;
        };
        //设置客户端同步模式为Additive
        nm.SceneManager.SetClientSynchronizationMode(LoadSceneMode.Additive);
    }

    void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request,NetworkManager.ConnectionApprovalResponse response)
    {
        if(request.ClientNetworkId == 0)
        {
            response.Approved = true;
            return;
        }

        if (MaxPlayers <= NetworkManager.Singleton.ConnectedClientsList.Count)
        {
            response.Approved = false;
            response.Reason = "房间已满，无法加入";
            return;
        }
        
        if (GameManager.Instance.IsPlaying)
        {
            response.Approved = false;
            response.Reason = "游戏已开始，无法加入";
            return;
        }
                
        ConnectionData data = ConnectionData.FromBytes(request.Payload);
        if (data.Pwd == AccessPassword)
        {
            PlayerData playerData = new() { name = data.Name, id = request.ClientNetworkId, isLive=true};
            MultiPlayManager.Instance.Players.Add(playerData);
            response.Approved = true;
        }
        else
        {
            Debug.Log("Approval fail" + request.ClientNetworkId);
            response.Approved = false;
            response.Reason = "Pwd error";
        }
    }

    public void StartClient(string ip,ushort port,string name,string pwd)
    {        
        var nm = NetworkManager.Singleton;
        //设置连接所用的参数
        nm.NetworkConfig.ConnectionData=new ConnectionData() { Name=name,Pwd=pwd}.ToBytes();
        UnityTransport transport = nm.NetworkConfig.NetworkTransport as UnityTransport; 
        transport.SetConnectionData(ip,port);
        
        isShutDown = false;        

        if (!nm.StartClient())
        {
            Debug.Log("客户端启动失败，请重新加入");
            nm.Shutdown();
            return;
        }

        StartCoroutine(ConnectWithTimeout(5));

        //在客户端同步模式为Addictive时，如果设置为true，客户端会卸载未与主机或服务器同步的任何场景
        nm.SceneManager.PostSynchronizationSceneUnloading = true;
        //当 NetworkSceneManager.PostSynchronizationSceneUnloading 设置为 true 且客户端同步模式为 LoadSceneMode.Additive 时，
        //如果设置了此回调，则对于即将卸载的每个场景，将调用设置的回调。
        //如果它返回 true ，则场景将被卸载；如果它返回 false ，则场景将保持加载状态。
        nm.SceneManager.VerifySceneBeforeUnloading += (scene) =>
        {
            if (scene.name == "Persistent") return false;
            return true;
        };

        //因为取消了对Persistent场景的同步会影响活动场景的设置所以需要手动同步活动场景
        NetworkManager.Singleton.SceneManager.OnSceneEvent += (sceneEvent) =>
        {
            if (sceneEvent.SceneEventType == SceneEventType.LoadComplete && sceneEvent.ClientId == NetworkManager.Singleton.LocalClientId)
            {
                if (sceneEvent.SceneName != "Persistent")
                {                    
                    SceneManager.SetActiveScene(sceneEvent.Scene);
                }
            }
        };
    }

    //超时检测
    IEnumerator ConnectWithTimeout(float timeout)
    {
        float start = Time.time;

        while (!NetworkManager.Singleton.IsConnectedClient && Time.time - start < timeout)
        {
            yield return null;
        }

        if (!NetworkManager.Singleton.IsConnectedClient)
        {
            Debug.LogError("连接超时！");
            ShutDown();
        }
    }

    public static string GetLocalIPV4()
    {
        foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
        {
            if (ni.OperationalStatus == OperationalStatus.Up &&
                ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
            {
                foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
                {
                    if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
                    {
                        return ip.Address.ToString();
                    }
                }
            }
        }
        return "127.0.0.1";
    }

    bool IsPortAvailable(int port)
    {
        try
        {
            using var udp = new UdpClient(port);
            return true;
        }
        catch (SocketException)
        {
            return false;
        }
    }

    public static string GetInvitationCode()
    {        
        var transport = NetworkManager.Singleton.NetworkConfig.NetworkTransport as UnityTransport;
        ushort port =transport.ConnectionData.Port;
        string invitationCode =GetLocalIPV4() + ":"+port;
        return invitationCode;
    }
}